package org.merapi.internal;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Class PolicyServer
 * Starts a PolicyServer on the specified port.
 * Can be started as main class, passing the port number as the first command line argument
 * @author Thomas Meyer, Less Rain (thomas@lessrain.com)
 *
 */
public class PolicyServer extends Thread {

    /**
     * If no argument is passed the server will listen on this port for connections
     */
    public static final int DEFAULT_PORT = 12344;
    public static final String[] DEFAULT_POLICY = new String[]{"*"};
    /**
     * The character sequence sent by the Flash Player to request a _policy file
     */
    public static final String POLICY_REQUEST = "<policy-file-request/>";
    public static final boolean DEBUG = true;

    /**
     * @param args	Use the first command line argument to set the port the server will listen on for connections
     */
    public static void main(String[] args) {
        int port = DEFAULT_PORT;
        try {
            if (args.length > 0) {
                port = Integer.parseInt(args[0]);
            }
        } catch (NumberFormatException e) {
        }

        // Start the PolicyServer
        (new PolicyServer(port, new String[]{"*:80"})).start();
    }
    /*
     * PolicyServer class variables
     */
    private int _port;
    private boolean _listening;
    public ServerSocket _socketServer;
    private String _policy;


    /**
         * PolicyServer constructor
         * @param port_ Sets the port that the PolicyServer listens on
         */
        public PolicyServer( int port_, String[] allowedHosts_ )
        {
                _port = port_;
                _listening=true;
                if (allowedHosts_==null) allowedHosts_ = DEFAULT_POLICY;
                _policy = buildPolicy(allowedHosts_);
        }

        private String buildPolicy( String[] allowedHosts_ )
        {
                StringBuffer policyBuffer = new StringBuffer();

                policyBuffer.append("<?xml version=\"1.0\"?><cross-domain-policy>");
                for (int i = 0; i < allowedHosts_.length; i++) {
                        String[] hostInfo = allowedHosts_[i].split(":");
                        String hostname = hostInfo[0];
//                      String ports = null;
//                      if (hostInfo.length>1) ports = hostInfo[1];
//                      else ports = "*";

                        policyBuffer.append("<allow-access-from domain=\""+hostname+"\" to-ports=\"12345\" />");
                }
                policyBuffer.append("</cross-domain-policy>");

                return policyBuffer.toString();
        }



//    /**
//     * PolicyServer constructor
//     * @param port_	Sets the port that the PolicyServer listens on
//     */
//    public PolicyServer(int port_, String[] allowedHosts_) {
//        _port = port_;
//        _listening = true;
//        if (allowedHosts_ == null) {
//            allowedHosts_ = DEFAULT_POLICY;
//        }
//        _policy = buildPolicy(allowedHosts_);
//    }
//    public static String hostname = null;
//    public static int Port = 0;
//
//    private String buildPolicy(String[] allowedHosts_) {
//        StringBuffer policyBuffer = new StringBuffer();
//
//        policyBuffer.append("<?xml version=\"1.0\"?><cross-domain-policy>");
//        for (int i = 0; i < allowedHosts_.length; i++) {
//            String[] hostInfo = allowedHosts_[i].split(":");
////            if (hostname == null) {
////                hostname = hostInfo[0];
////            }
//			String hostname = hostInfo[0];
////			String ports = null;
////			if (hostInfo.length>1) ports = hostInfo[1];
////			else ports = "*";
//
////			policyBuffer.append("<allow-access-from domain=\""+hostname+"\" to-ports=\"12345\" />");
//            policyBuffer.append("<allow-access-from domain=\"" + hostname + "\" to-ports=\"12345\" />");// secure=\"false\" />");
//
//        }
//        policyBuffer.append("</cross-domain-policy>");
//
//        return policyBuffer.toString();
//    }

    /**
     * Thread run method, accepts incoming connections and creates SocketConnection objects to handle requests
     */
    public void run() {
        try {
            _listening = true;

            // Start listening for connections
            _socketServer = new ServerSocket(_port, 1);
            if (DEBUG) {
                System.out.println("PolicyServer listening on port " + _port);
            }

            while (_listening) {
//                _listening = false;



                // Wait for a connection and accept it
                Socket socket = _socketServer.accept();


                System.out.println("RemoteSocketAddress (Pol): " + socket.getRemoteSocketAddress().toString());
                System.out.println("LockalSocketAddress (Pol): " + socket.getRemoteSocketAddress().toString());

                System.out.println("InetAddress (Pol): " + socket.getInetAddress().getHostAddress());
//                PolicyServer.hostname = socket.getInetAddress().getHostAddress();

                try {
//                    if (DEBUG) {
                        System.out.println("PolicyServer got a connection on port " + _port);
//                    }
                    // Start a new connection thread
                    (new SocketConnection(socket)).start();
                    

                } catch (Exception e) {
//                    if (DEBUG) {
                        System.out.println("Exception: " + e.getMessage());
//                    }
                }
				try
				{
					// Wait for a sec until a new connection is accepted to avoid flooding
					sleep(1000);
				}
				catch (InterruptedException e) {}
            }
        } catch (IOException e) {
//            if (DEBUG) {
                System.out.println("IO Exception: " + e.getMessage());
//            }
        }
        try {
            _socketServer.close();
//        while (!_socketServer.isClosed()) {
//            try {
//                _socketServer.close();
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//        }
////        if (DEBUG) {
//            System.out.println("PolicyServer closing");
////        }
////        Bridge.open();
        } catch (IOException ex) {
            Logger.getLogger(PolicyServer.class.getName()).log(Level.SEVERE, null, ex);
        }
//        while (!_socketServer.isClosed()) {
//            try {
//                _socketServer.close();
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//        }
////        if (DEBUG) {
//            System.out.println("PolicyServer closing");
////        }
////        Bridge.open();
    }

    /**
     * Local class SocketConnection
     * For every accepted connection one SocketConnection is created.
     * It waits for the _policy file request, returns the _policy file and closes the connection immediately
     * @author Thomas Meyer, Less Rain (thomas@lessrain.com)
     *
     */
    class SocketConnection extends Thread {

        private Socket _socket;
        private BufferedReader _socketIn;
        private PrintWriter _socketOut;

        /**
         * Constructor takes the Socket object for this connection
         * @param socket_	Socket connection to a client created by the PolicyServer main thread
         */
        public SocketConnection(Socket socket_) {
            _socket = socket_;

        }

        /**
         * Thread run method waits for the _policy request, returns the poilcy file and closes the connection
         */
        public void run() {
            try {
                // initialize socket and readers/writers
                _socket.setSoTimeout(10000);
                _socketIn = new BufferedReader(new InputStreamReader(_socket.getInputStream()));
                _socketOut = new PrintWriter(_socket.getOutputStream(), true);
            } catch (IOException e) {
                if (DEBUG) {
                    System.out.println("IO Exception " + e.getMessage());
                }
                return;
            }

            readPolicyRequest();
        }

        /**
         * Wait for and read the _policy request sent by the Flash Player
         * Return the _policy file and close the Socket connection
         */
        private void readPolicyRequest() {
            try {
                // Read the request and compare it to the request string defined in the constants.
                // If the proper _policy request has been sent write out the _policy file
                if (POLICY_REQUEST.equals(read())) {
                    write(_policy);
                }
            } catch (Exception e) {
                if (DEBUG) {
                    System.out.println("Exception " + e.getMessage());
                }
            }
            close();
        }

        /**
         * Read until a zero character is sent or a maximum of 100 character
         * @return The character sequence read
         * @throws IOException
         * @throws EOFException
         * @throws InterruptedIOException
         */
        private String read() throws IOException, EOFException, InterruptedIOException {
            StringBuffer buffer = new StringBuffer();
            int codePoint;
            boolean zeroByteRead = false;

            if (DEBUG) {
                System.out.println("Reading...");
            }
            do {
                codePoint = _socketIn.read();
                if (codePoint == 0) {
                    zeroByteRead = true;
                } else {
                    buffer.appendCodePoint(codePoint);
                }
            } while (!zeroByteRead && buffer.length() < 100);
            if (DEBUG) {
                System.out.println("Read: " + buffer.toString());
            }



            return buffer.toString();


        }

        /**
         * Writes a String to the client
         * @param msg	Text to be sent to the client (_policy file)
         */
        public void write(String msg) {
            _socketOut.println(msg + "\u0000");
            _socketOut.flush();
            if (DEBUG) {
                System.out.println("Wrote: " + msg);
            }
        }

        /**
         * Close the Socket connection an set everything to null. Prepared for garbage collection
         */
        public void close() {
            try {
                if (_socket != null) {
                    _socket.close();
                }
                if (_socketOut != null) {
                    _socketOut.close();
                }
                if (_socketIn != null) {
                    _socketIn.close();
                }
            } catch (IOException e) {
            }

            _socketIn = null;
            _socketOut = null;
            _socket = null;
        }
    }
}
